package com.flame.shapeville.util;

import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.nio.file.Paths;
import java.util.*;

/**
 * Generic database connector for handling YAML file-based persistence operations.
 * <p>
 * Supports querying, updating, and saving objects to YAML files using SnakeYAML library.
 *
 * @param <T> Type of the data model being managed
 * @author Flame Dev Team
 * @version 1.0
 * @since 2025-05-01
 */
public class DbConnector<T> {
    /**
     * File path of the YAML database.
     */
    private final String url;

    /**
     * YAML instance used for parsing and dumping.
     */
    private final Yaml yaml;

    /**
     * Class type of the managed data model.
     */
    private final Class<T> aClass;

    /**
     * Constructs a new DbConnector instance.
     *
     * @param url   Path to the YAML file
     * @param aClass Class type of the data model
     */
    public DbConnector(String url, Class<T> aClass) {
        this.url = Paths.get(System.getProperty("user.dir")).getParent().resolve(url).toString();
        this.aClass = aClass;

        DumperOptions options = new DumperOptions();
        options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
        options.setIndent(2);
        JfRepresenter representer = new JfRepresenter(options);
        yaml = new Yaml(representer, options);
    }

    /**
     * Queries a singleton object based on root key.
     *
     * @param root Root key in the YAML file
     * @return Deserialized object or null if not found
     */
    public T querySingleton(String root) {
        try (InputStream inputStream = new FileInputStream(url)) {
            Map<String, Object> db = yaml.load(inputStream);
            if (db == null || db.isEmpty()) {
                return null;
            }
            String dump = yaml.dump(db.get(root));
            if (dump == null || dump.equals("null\n")) {
                dump = yaml.dump(db.get(Integer.parseInt(root)));
            }
            return yaml.loadAs(dump, aClass);
        } catch (IOException e) {
            throw new RuntimeException("Failed to read from input stream", e);
        }
    }

    /**
     * Queries the first entry in the YAML file.
     *
     * @return First deserialized object or null
     */
    public T querySingleton() {
        try (InputStream inputStream = new FileInputStream(url)){
            LinkedHashMap<String, Object> db = yaml.load(inputStream);
            if (db == null || db.isEmpty()) {
                return null;
            }
            String dump = yaml.dump(db.firstEntry().getValue());
            return yaml.loadAs(dump, aClass);
        } catch (IOException e) {
            throw new RuntimeException("Failed to reset input stream", e);
        }
    }

    /**
     * Queries a singleton object matching a specific field value.
     *
     * @param field Field name to match
     * @param value Value to compare against
     * @return Deserialized object or null if not found
     */
    public T querySingleton(String field, String value) {
        try (InputStream inputStream = new FileInputStream(url)) {
            Map<String, Object> db = yaml.load(inputStream);
            if (db == null || db.isEmpty()) {
                return null;
            }
            for (Map.Entry<String, Object> entry : db.entrySet()) {
                if (entry.getValue() instanceof LinkedHashMap) {
                    LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) entry.getValue();
                    if (map.get(field).equals(value)) {
                        String dump = yaml.dump(map);
                        return yaml.loadAs(dump, aClass);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to read from input stream", e);
        }
        return null;
    }

    /**
     * Queries list of objects matching a specific field value.
     *
     * @param field Field name to match
     * @param value Value to compare against
     * @return List of matched objects
     */
    public List<T> query(String field, String value) {
        List<T> results = new ArrayList<>();
        try  (InputStream inputStream = new FileInputStream(url)){
            Map<String, Object> db = yaml.load(inputStream);
            if (db == null || db.isEmpty()) {
                return results;
            }
            for (Map.Entry<String, Object> entry : db.entrySet()) {
                if (entry.getValue() instanceof LinkedHashMap) {
                    LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) entry.getValue();
                    if (value.equals(map.get(field))) {
                        String dump = yaml.dump(map);
                        T obj = yaml.loadAs(dump, aClass);
                        results.add(obj);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to read from input stream", e);
        }
        return results;
    }

    /**
     * Queries list of objects matching multiple field-value pairs.
     *
     * @param params Map of field-value pairs to match
     * @return List of matched objects
     */
    public List<T> query(Map<String, String> params) {
        List<T> results = new ArrayList<>();
        try  (InputStream inputStream = new FileInputStream(url)){
            Map<String, Object> db = yaml.load(inputStream);
            if (db == null || db.isEmpty()) {
                return results;
            }
            for (Map.Entry<String, Object> entry : db.entrySet()) {
                if (entry.getValue() instanceof LinkedHashMap) {
                    LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) entry.getValue();
                    if (params.entrySet().stream().allMatch(param -> map.get(param.getKey()).equals(param.getValue()))) {
                        String dump = yaml.dump(map);
                        T obj = yaml.loadAs(dump, aClass);
                        results.add(obj);
                    }
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to read from input stream", e);
        }
        return results;
    }

    /**
     * Queries all entries in the YAML file.
     *
     * @return List of all deserialized objects
     */
    public List<T> queryAll() {
        List<T> results = new ArrayList<>();
        try  (InputStream inputStream = new FileInputStream(url)){
            Map<String, Object> db = yaml.load(inputStream);
            if (db == null || db.isEmpty()) {
                return results;
            }
            for (Map.Entry<String, Object> entry : db.entrySet()) {
                if (entry.getValue() instanceof LinkedHashMap) {
                    LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) entry.getValue();
                    String dump = yaml.dump(map);
                    T obj = yaml.loadAs(dump, aClass);
                    results.add(obj);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException("Failed to read from input stream", e);
        }
        return results;
    }

    /**
     * Retrieves the last key from the YAML file.
     *
     * @return Last key as String or null
     */
    public String getLastKey() {
        try  (InputStream inputStream = new FileInputStream(url)){
            Map<String, Object> db = yaml.load(inputStream);
            return Utils.getLastKeyOfLinkedHashMap(db);
        } catch (IOException e) {
            throw new RuntimeException("Failed to read from input stream", e);
        }
    }

    /**
     * Updates or saves an object to the YAML file.
     *
     * @param key Key to store under
     * @param value Object to save
     */
    public void updateOrSave(String key, Object value) {
        try (InputStream inputStream = new FileInputStream(url)){
            Map<String, Object> db = yaml.load(inputStream);
            if (db == null) {
                db = new LinkedHashMap<>();
            }
            if (!Utils.isBasicDataType(value)) {
                value = yaml.loadAs(yaml.dumpAsMap(value), LinkedHashMap.class);
            }
            db.put(key, value);
            String updatedContent = yaml.dump(db);
            try (FileOutputStream fos = new FileOutputStream(url)) {
                fos.write(updatedContent.getBytes());
            }

        } catch (IOException e) {
            throw new RuntimeException("Failed to save to YML file", e);
        }
    }
}
